sequenceDiagram
participant Browser
participant IIS
participant EntraMiddleware as EntraTokenValidationMiddleware
participant AuthHandler as AppAuthorizationMessageHandler
participant Resolver as AdIdentityResolver
participant Domain as PrincipalUserQuery (MediatR)
Browser->>IIS: GET /api/... (no explicit login)<br/>MSAL silently acquires token via PRT/Seamless SSO
Note over Browser,IIS: Authorization: Bearer eyJ...
IIS->>EntraMiddleware: OWIN pipeline
EntraMiddleware->>EntraMiddleware: Validate JWT signature via OIDC discovery keys
EntraMiddleware->>EntraMiddleware: Stamp ClaimsPrincipal with kf_auth_source=Bearer
EntraMiddleware->>EntraMiddleware: Set Thread.CurrentPrincipal + HttpContext.Current.User
EntraMiddleware->>AuthHandler: next.Invoke()
AuthHandler->>Resolver: ResolveAccountName(request)
Resolver->>Resolver: Detect kf_auth_source=Bearer claim
Resolver->>Resolver: Extract preferred_username → upn → email
Resolver->>Resolver: MapToAccountName() → internal AD login
Resolver-->>AuthHandler: "SA1\DOEJO"
AuthHandler->>Domain: PrincipalUserQuery { ActiveDirectoryAccount="SA1\DOEJO" }
Domain-->>AuthHandler: PrincipalUser
AuthHandler-->>Browser: 200 OK
8 API Authentication
The current NTLM authentication protocol is ‘end of life’ in early 2027, so we need to update the API authentication layer to a more modern protocol. The company uses AD Entra.
8.0.1 Flow 1 — Human SSO (Entra bearer token, domain-joined device)
8.0.2 Flow 2 — Human SSO (Windows / NTLM fallback, mode = Dual)
sequenceDiagram
participant Browser
participant IIS
participant EntraMiddleware as EntraTokenValidationMiddleware
participant AuthHandler as AppAuthorizationMessageHandler
participant Resolver as AdIdentityResolver
participant Domain as PrincipalUserQuery (MediatR)
Browser->>IIS: GET /api/... (no bearer token)
Note over Browser,IIS: IIS Windows Auth negotiates NTLM/Kerberos
IIS->>EntraMiddleware: OWIN pipeline
EntraMiddleware->>EntraMiddleware: No Authorization: Bearer header → no-op
EntraMiddleware->>AuthHandler: next.Invoke()
AuthHandler->>Resolver: ResolveAccountName(request)
Resolver->>Resolver: No kf_auth_source=Bearer claim
Resolver->>Resolver: Mode = Dual + EnableWindowsFallback = true
Resolver->>Resolver: Read HttpContext.Current.User.Identity.Name
Resolver-->>AuthHandler: "SA1\DOEJO"
AuthHandler->>Domain: PrincipalUserQuery { ActiveDirectoryAccount="SA1\DOEJO" }
Domain-->>AuthHandler: PrincipalUser
AuthHandler-->>Browser: 200 OK
8.0.3 Flow 3 — System-to-system (existing ExternalApplicationHandler — unchanged)
sequenceDiagram
participant CallingService as Calling Service
participant IIS
participant EntraMiddleware as EntraTokenValidationMiddleware
participant AuthHandler as AppAuthorizationMessageHandler
participant ExtHandler as ExternalApplicationHandler
participant Domain as PrincipalUserQuery (MediatR)
CallingService->>IIS: POST /api/... + cert / shared-secret header
IIS->>EntraMiddleware: OWIN pipeline
EntraMiddleware->>EntraMiddleware: No bearer token (or token ignored) → no-op
EntraMiddleware->>AuthHandler: next.Invoke()
AuthHandler->>ExtHandler: IsRequestFromExternalApplication()?
ExtHandler-->>AuthHandler: true
AuthHandler->>ExtHandler: GetAuthorizedUserName()
ExtHandler-->>AuthHandler: "svc-account@knightfrank.com"
AuthHandler->>Domain: PrincipalUserQuery { ActiveDirectoryAccount="svc-account@..." }
Domain-->>AuthHandler: PrincipalUser
AuthHandler-->>CallingService: 200 OK
Note over AuthHandler,ExtHandler: Entire resolver path is bypassed.<br/>No change from current behaviour.
8.1 Current state in your API
- Windows auth is enabled in config: Web.config:630
- Anonymous users are denied: Web.config:631
- User identity is read from IIS Windows principal name in the auth handler: AppAuthorizationMessageHandler.cs
- SignalR does the same pattern: SignalrAuthorizationAttribute.cs
8.2 Target approach (recommended)
- Use Microsoft Entra ID backed by AD accounts (hybrid or cloud-only).
- Frontend gets OAuth/OIDC tokens silently with SSO.
- API validates bearer JWT tokens.
- API maps token claims to your existing user model and continues to use PrincipalUser.
8.3 How users still avoid login prompts
- Domain-joined or Entra-joined devices with Seamless SSO/PRT can get tokens silently.
- Browser sessions use SSO cookies/PRT and usually do not show login pages.
- Non-compliant devices can still be handled with Conditional Access rules.
8.4 What to change in this solution
Phase 1: Add token auth without breaking NTLM 1. Add OWIN JWT bearer middleware in startup. 1. Keep current handler, but make identity source pluggable: * If bearer token exists, read from claims like preferred_username or upn. * Else fallback to existing HttpContext.Current.User.Identity.Name. 1. Keep existing webhook/system-account paths as-is in the handler.
8.5 Phase 2: Switch primary auth to AD token SSO
- Change API config from Windows auth requirement to token validation path.
- Update frontend to use MSAL and call API with bearer tokens.
- Update SignalR auth to validate token/claims (or use negotiated auth compatible with your SignalR version).
8.6 Phase 3: retire NTLM
- Disable IIS Windows authentication for the API.
- Enable anonymous requests at IIS layer.
- Rely on JWT middleware and API authorization.
- Remove NTLM-specific code paths when stable.
8.7 Minimal code design change
- Introduce an identity resolver abstraction:
- ResolveAccountNameFromTokenOrWindowsPrincipal()
- Use that in:
- AppAuthorizationMessageHandler.cs
- SignalrAuthorizationAttribute.cs
- Keep your existing PrincipalUserQuery flow unchanged so permissions/business logic remain stable.
8.8 Claims mapping guidance
- Prefer claim order:
- preferred_username
- upn
- If your database stores SAM format like DOMAIN, add a mapping step from UPN to SAM where needed.
- Store Entra object id as a stable alternate key for future-proofing.
8.9 Risk points to plan for
- Legacy browsers and old WebView clients may not perform silent SSO cleanly.
- Service-to-service and webhook flows must remain certificate or app-token based.
- SignalR auth flow may need special handling depending on transport and token forwarding.
- Cross-domain frontend/API requires strict CORS + token audience checks.
8.10 Suggested rollout pattern
- Dual-auth window: support NTLM and bearer for a short period.
- Pilot one department.
- Monitor unauthorized responses and claim-mapping misses.
- Complete cutover and disable NTLM.